package drds.plus.datanode.select;


import drds.plus.common.jdbc.SqlPreParser;
import drds.plus.common.utils.TStringUtil;
import drds.plus.datanode.configuration.DataNodeExtraConfiguration;
import drds.plus.datanode.configuration.DataSourceIndexAndNeedRetryIfDailed;
import drds.plus.datanode.executor.Executor;
import drds.plus.datanode.select.equity_manager_select.GroupNotAvaliableException;
import drds.plus.datasource.exception_sorter.ExceptionSorter;
import lombok.extern.slf4j.Slf4j;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.*;

/**
 * 单个数据源有效性判断在这个类判断
 */
@Slf4j
public abstract class AbstractSelector implements Selector {

    private static final int default_retryBadDbInterval = 0; // milliseconds
    protected static int retryBadDbInterval = default_retryBadDbInterval; // milliseconds

    protected boolean readable = false;
    protected boolean isSupportRetry = true; // 默认情况下支持重试
    protected DataNodeExtraConfiguration dataNodeExtraConfiguration;

    protected ExceptionSorter exceptionSorter = null;
    private String id = "undefined"; // id值未使用

    public AbstractSelector() {
    }

    public AbstractSelector(String id) {
        this.id = id;
    }

    public void setReadable(boolean readable) {
        this.readable = readable;
    }

    public boolean isSupportRetry() {
        return isSupportRetry;
    }

    public void setSupportRetry(boolean isSupportRetry) {
        this.isSupportRetry = isSupportRetry;
    }

    public <T> T getConnectionWrapperAndExecuteAndRetryIfFailed(Executor<T> executor, int times, Object... args) throws SQLException {
        return this.getConnectionWrapperAndExecuteAndRetryIfFailed(new LinkedHashMap<DataSource, SQLException>(0), executor, times, args);
    }

    public <T> T getConnectionWrapperAndExecuteAndRetryIfFailed(Map<DataSource, SQLException> failedDataSourceToSQLExceptionMap, Executor<T> executor, int times, Object... args) throws SQLException {
        // dataSourceIndex放在args最后一个.以后改动要注意
        // local set dataSourceIndexAndNeedRetryIfDailed was placed first
        DataSourceIndexAndNeedRetryIfDailed dataSourceIndexAndNeedRetryIfDailed = null;
        if (args != null && args.length > 0) {
            dataSourceIndexAndNeedRetryIfDailed = (DataSourceIndexAndNeedRetryIfDailed) args[args.length - 1];
        }

        if (dataNodeExtraConfiguration != null) {
            Boolean defaultMain = dataNodeExtraConfiguration.isDefaultMain();
            Map<String, String> tableDsIndexMap = dataNodeExtraConfiguration.getTableDsIndexMap();
            Map<String, String> sqlDsIndexMap = dataNodeExtraConfiguration.getSqlDsIndexMap();
            Set<String> sqlForbidSet = dataNodeExtraConfiguration.getSqlForbidSet();

            // 1.when batch ,args have no sql_process parameter,so,should check
            // the args
            // 2.table dataSourceIndexAndNeedRetryIfDailed relation have 2th priority
            // 3.sql_process dataSourceIndexAndNeedRetryIfDailed relation have 3th priority
            if (args != null && args.length > 0 && args[0] instanceof String) {
                if (sqlForbidSet != null && sqlForbidSet.size() > 0) {
                    String sql = (String) args[0];
                    String nomalSql = TStringUtil.fillTabWithSpace(sql);
                    boolean isForbidden = false;
                    if (sqlForbidSet.contains(nomalSql)) {
                        isForbidden = true;
                    }
                    if (!isForbidden) {
                        String actualTable = SqlPreParser.findTableName(nomalSql);
                        for (String configSql : sqlForbidSet) {
                            String nomalConfigSql = TStringUtil.fillTabWithSpace(configSql);
                            String actualConfigTable = SqlPreParser.findTableName(nomalConfigSql);
                            if (TStringUtil.isTableFatherAndSon(actualConfigTable, actualTable)) {
                                nomalConfigSql = nomalConfigSql.replaceAll(actualConfigTable, actualTable);
                            }
                            if (nomalConfigSql.equals(nomalSql)) {
                                isForbidden = true;
                                break;
                            }
                        }
                    }
                    if (isForbidden) {
                        String message = "sql_process : '" + sql;
                        log.error(message);
                        throw new RuntimeException(message);
                    }
                }

                if (tableDsIndexMap != null && tableDsIndexMap.size() > 0 && (dataSourceIndexAndNeedRetryIfDailed == null || dataSourceIndexAndNeedRetryIfDailed.dataSourceIndex == NOT_EXIST_USER_SPECIFIED_INDEX)) {
                    String sql = (String) args[0];
                    String actualTable = SqlPreParser.findTableName(sql);
                    String index = tableDsIndexMap.get(actualTable);
                    if (index == null || index == NOT_EXIST_USER_SPECIFIED_INDEX) {
                        Set<String> tableSet = tableDsIndexMap.keySet();
                        for (String configTable : tableSet) {
                            if (TStringUtil.isTableFatherAndSon(configTable, actualTable)) {
                                index = tableDsIndexMap.get(configTable);
                                break;
                            }
                        }
                    }
                    // 这里换了引用，外部引用是不会变的，但是最终清理的时候是同一个线程
                    // 上的threadlocal变量，所以应该不会有影响。
                    if (index != null)
                        dataSourceIndexAndNeedRetryIfDailed = new DataSourceIndexAndNeedRetryIfDailed(index, false);
                }

                if (sqlDsIndexMap != null && sqlDsIndexMap.size() > 0 && (dataSourceIndexAndNeedRetryIfDailed == null || dataSourceIndexAndNeedRetryIfDailed.dataSourceIndex == NOT_EXIST_USER_SPECIFIED_INDEX)) {
                    String sql = ((String) args[0]).toLowerCase();
                    String nomalSql = TStringUtil.fillTabWithSpace(sql);
                    String index = sqlDsIndexMap.get(nomalSql);
                    if (index == null || index == NOT_EXIST_USER_SPECIFIED_INDEX) {
                        String actualTable = SqlPreParser.findTableName(nomalSql);
                        Set<String> sqlSet = sqlDsIndexMap.keySet();
                        for (String configSql : sqlSet) {
                            String nomalConfigSql = TStringUtil.fillTabWithSpace(configSql);
                            String actualConfigTable = SqlPreParser.findTableName(nomalConfigSql);
                            if (TStringUtil.isTableFatherAndSon(actualConfigTable, actualTable)) {
                                nomalConfigSql = nomalConfigSql.replaceAll(actualConfigTable, actualTable);
                            }
                            if (nomalConfigSql.equals(nomalSql)) {
                                index = sqlDsIndexMap.get(configSql);
                                break;
                            }
                        }
                    }

                    if (index != null)
                        dataSourceIndexAndNeedRetryIfDailed = new DataSourceIndexAndNeedRetryIfDailed(index, false);
                }
            }

            // 1.this case simple handled,just set dataSourceIndexAndNeedRetryIfDailed=0
            // 2.default nonspi have 4th priority
            if ((dataSourceIndexAndNeedRetryIfDailed == null || dataSourceIndexAndNeedRetryIfDailed.dataSourceIndex == NOT_EXIST_USER_SPECIFIED_INDEX) && defaultMain) {
                dataSourceIndexAndNeedRetryIfDailed = new DataSourceIndexAndNeedRetryIfDailed("0", false);
            }
        }

        // 如果业务层直接指定了一个数据源，就直接在指定的数据源上进行查询更新操作，失败时不再重试。
        if (dataSourceIndexAndNeedRetryIfDailed != null && dataSourceIndexAndNeedRetryIfDailed.dataSourceIndex != NOT_EXIST_USER_SPECIFIED_INDEX) {
            DataSourceHolder dataSourceHolder = findDataSourceHolderByDataSourceIndex(dataSourceIndexAndNeedRetryIfDailed.dataSourceIndex);
            if (dataSourceHolder == null) {
                throw new IllegalArgumentException("can't find " + dataSourceIndexAndNeedRetryIfDailed);
            }
            return createConnectionWrapperAndExecute(dataSourceHolder, failedDataSourceToSQLExceptionMap, dataSourceIndexAndNeedRetryIfDailed, executor, times, args);
        } else {
            return selectDataSourceHolderAndGetConnectionWrapperAndExecuteAndRetryIfFailed(failedDataSourceToSQLExceptionMap, executor, times, args);
        }
    }

    /**
     * 在一个数据库上执行，有单线程试读
     */
    protected <T> T createConnectionWrapperAndExecute(DataSourceHolder dataSourceHolder, Map<DataSource, SQLException> failedDataSourceToSQLExceptionMap, Executor<T> executor, int times, Object... args) throws SQLException {
        //先异常判断
        List<SQLException> sqlExceptionLinkedList = new LinkedList<SQLException>();
        if (failedDataSourceToSQLExceptionMap != null) {
            sqlExceptionLinkedList.addAll(failedDataSourceToSQLExceptionMap.values());
        }
        if (failedDataSourceToSQLExceptionMap != null && failedDataSourceToSQLExceptionMap.containsKey(dataSourceHolder.dataSourceWrapper)) {
            return executor.onSqlException(sqlExceptionLinkedList, exceptionSorter, args);
        }
        //
        try {
            //可用恢复
            if (dataSourceHolder.isNotAvailable) {
                boolean toTry = System.currentTimeMillis() - dataSourceHolder.lastRetryTime > retryBadDbInterval;
                if (toTry && dataSourceHolder.lock.tryLock()) {
                    try {
                        T t = executor.createConnectionWrapperAndExecute(dataSourceHolder.dataSourceWrapper, args); // 同一个时间只会有一个线程继续使用这个数据源。
                        dataSourceHolder.isNotAvailable = false; // 用一个线程重试，执行成功则标记为可用，自动恢复
                        return t;
                    } finally {
                        dataSourceHolder.lastRetryTime = System.currentTimeMillis();
                        dataSourceHolder.lock.unlock();
                    }
                } else {
                    sqlExceptionLinkedList.add(new GroupNotAvaliableException("dsKey:" + dataSourceHolder.dataSourceWrapper.getDataSourceId() + " not Available,toTry:" + toTry));
                    return executor.onSqlException(sqlExceptionLinkedList, exceptionSorter, args);
                }
            } else {
                return executor.createConnectionWrapperAndExecute(dataSourceHolder.dataSourceWrapper, args); // 有一次成功直接返回
            }
        } catch (SQLException e) {
            if (exceptionSorter.isFatalException(e)) {
                dataSourceHolder.isNotAvailable = true;
            }
            sqlExceptionLinkedList.add(e);
            return executor.onSqlException(sqlExceptionLinkedList, exceptionSorter, args);
        }
    }

    /**
     * 在指定单库上执行，不过调用方为直接设定group index的，如果指定的数据库不可用， 然后又指定了ThreadLocalString.RETRY_IF_SET_DS_INDEX 为true,那么走权重(如果有权重的话)
     */
    protected <T> T createConnectionWrapperAndExecute(DataSourceHolder dataSourceHolder, Map<DataSource, SQLException> failedDataSourceToSQLExceptionMap, DataSourceIndexAndNeedRetryIfDailed dataSourceIndexAndNeedRetryIfDailed, Executor<T> executor, int times, Object... args) throws SQLException {
        //先异常判断
        List<SQLException> sqlExceptionLinkedList = new LinkedList<SQLException>();
        if (failedDataSourceToSQLExceptionMap != null) {
            sqlExceptionLinkedList.addAll(failedDataSourceToSQLExceptionMap.values());
        }
        if (failedDataSourceToSQLExceptionMap != null && failedDataSourceToSQLExceptionMap.containsKey(dataSourceHolder.dataSourceWrapper)) {
            return executor.onSqlException(sqlExceptionLinkedList, exceptionSorter, args);
        }
        //
        try {
            //可用恢复
            if (dataSourceHolder.isNotAvailable) {
                boolean needRetry = System.currentTimeMillis() - dataSourceHolder.lastRetryTime > retryBadDbInterval;
                if (needRetry && dataSourceHolder.lock.tryLock()) {
                    try {
                        T t = executor.createConnectionWrapperAndExecute(dataSourceHolder.dataSourceWrapper, args); // 同一个时间只会有一个线程继续使用这个数据源。
                        dataSourceHolder.isNotAvailable = false; // 用一个线程重试，执行成功则标记为可用，自动恢复
                        return t;
                    } finally {
                        dataSourceHolder.lastRetryTime = System.currentTimeMillis();
                        dataSourceHolder.lock.unlock();
                    }
                } else if (dataSourceIndexAndNeedRetryIfDailed.needRetryIfDailed) {
                    // FIXME:这里需要看下，如果在事务中，是否应该重试。
                    return selectDataSourceHolderAndGetConnectionWrapperAndExecuteAndRetryIfFailed(failedDataSourceToSQLExceptionMap, executor, times, args);
                } else {
                    sqlExceptionLinkedList.add(new GroupNotAvaliableException("dsKey:" + dataSourceHolder.dataSourceWrapper.getDataSourceId() + " not Available,toTry:" + times));
                    return executor.onSqlException(sqlExceptionLinkedList, exceptionSorter, args);
                }
            } else {
                return executor.createConnectionWrapperAndExecute(dataSourceHolder.dataSourceWrapper, args); // 有一次成功直接返回
            }
        } catch (SQLException e) {
            if (exceptionSorter.isFatalException(e)) {
                dataSourceHolder.isNotAvailable = true;
            }
            sqlExceptionLinkedList.add(e);
            return executor.onSqlException(sqlExceptionLinkedList, exceptionSorter, args);
        }
    }


    public String getId() {
        return id;
    }

    protected abstract DataSourceHolder findDataSourceHolderByDataSourceIndex(String dataSourceIndex);

    /**
     * 需要重新获取数据源进行执行
     */
    protected abstract <T> T selectDataSourceHolderAndGetConnectionWrapperAndExecuteAndRetryIfFailed(Map<DataSource, SQLException> failedDataSources, Executor<T> tryer, int times, Object... args) throws SQLException;

}
